package com.renren.project.db.utils;

import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.renren.project.db.bean.PageInfo;
import com.renren.project.db.exception.RRException;
import com.renren.project.db.utils.anno.Condition;
import com.renren.project.db.utils.anno.TableAlias;
import com.renren.project.db.utils.bean.BaidonQueryWrapper;
import com.renren.project.db.utils.bean.CheckQuery;
import com.renren.project.db.utils.bean.ConditionType;
import com.renren.project.db.utils.bean.Order;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;

@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class WrapperUtil {

    public static final PropertyNamingStrategy.SnakeCaseStrategy SNAKE_CASE_STRATEGY = new PropertyNamingStrategy.SnakeCaseStrategy();

    public static <T, R> Page<R> getPage(PageInfo pageInfo, Object cond, BiFunction<Page<T>, QueryWrapper<T>, Page<R>> biFunction) {
        return biFunction.apply(pageInfo.page(), getWrapper(cond));
    }

    public static <T, R> List<R> getList(Object cond, Function<QueryWrapper<T>, List<R>> biFunction) {
        return biFunction.apply(getWrapper(cond));
    }

    public static <T> void check(Function<LambdaQueryWrapper<T>, Integer> function, List<CheckQuery<T>> checkQueries) {
        List<CheckQuery<T>> commons = checkQueries.stream()
                .filter(CheckQuery::isCommon)
                .collect(Collectors.toList());
        List<CheckQuery<T>> queries = checkQueries.stream()
                .filter(c -> !c.isCommon())
                .collect(Collectors.toList());
        for (CheckQuery<T> query : queries) {
            Object value = query.getValue();
            if (!StringUtils.isEmpty(value)) {
                LambdaQueryWrapper<T> lambdaQueryWrapper = new LambdaQueryWrapper<>();
                for (CheckQuery<T> common : commons) {
                    Object val = common.getValue();
                    if (!StringUtils.isEmpty(val)) {
                        if (common.isEq()) {
                            lambdaQueryWrapper.eq(common.getFunction(), common.getValue());
                        } else {
                            lambdaQueryWrapper.ne(common.getFunction(), common.getValue());
                        }
                    }
                }
                if (query.isEq()) {
                    lambdaQueryWrapper.eq(query.getFunction(), query.getValue());
                } else {
                    lambdaQueryWrapper.ne(query.getFunction(), query.getValue());
                }
                Integer count = function.apply(lambdaQueryWrapper);
                RRException.isZero(count, query.getMessage());
            }
        }
    }

    @SneakyThrows
    public static <T> UpdateWrapper<T> getUpdateWrapper(Object obj, Class<T> clazz) {
        UpdateWrapper<T> updateWrapper = new UpdateWrapper<>();
        String keyName = TableInfoHelper.getTableInfo(clazz).getKeyProperty();
        for (Field field : obj.getClass().getDeclaredFields()) {
            String fieldName = field.getName();
            if (fieldName.equals(keyName)) {
                updateWrapper.eq(SNAKE_CASE_STRATEGY.translate(fieldName), FieldUtils.readField(field, obj, true));
            } else {
                updateWrapper.set(SNAKE_CASE_STRATEGY.translate(fieldName), FieldUtils.readField(field, obj, true));
            }
        }
        return updateWrapper;
    }

    /**
     * 根据查询类的注解生产构建QueryWrapper
     * 默认行为：
     * 1、字符串默认模糊匹配
     * 2、Order、List<Order>默认进行排序
     * 3、字段名以Begin、End结尾默认会进行大于等于和小于等于匹配，如createTimeBegin表示create_time>=
     * 3、其他类型默认精确匹配
     *
     * @param cond 查询对象
     * @param <T>  泛型
     * @return QueryWrapper
     */
    @SuppressWarnings("unchecked")
    @SneakyThrows
    public static <T> BaidonQueryWrapper<T> getWrapper(Object cond) {
        BaidonQueryWrapper<T> queryWrapper = new BaidonQueryWrapper<>();
        Class<?> clazz = cond.getClass();
        TableAlias classTableAlias = clazz.getAnnotation(TableAlias.class);
        final String globalAlias = getAlias(classTableAlias, "");
        Field[] fields = clazz.getDeclaredFields();
        for (Field f : fields) {
            Class<?> type = f.getType();
            boolean fieldTypeByList = type.isAssignableFrom(List.class);
            Object value = FieldUtils.readField(f, cond, true);
            Condition condition = f.getAnnotation(Condition.class);
            // 字段值默认不为null
            boolean isNull = false;
            if (StringUtils.isEmpty(value)) {
                if (condition == null || condition.nullHandler() == ConditionType.NULL_HANDLER) {
                    continue;
                }
                isNull = true;
            }
            TableAlias fieldTableAlias = f.getAnnotation(TableAlias.class);
            String fieldAlias = getAlias(fieldTableAlias, globalAlias);
            String name = fieldAlias + "`" + SNAKE_CASE_STRATEGY.translate(f.getName()) + "`";
            // 默认行为
            if (condition == null) {
                // 默认字符串采用模糊匹配匹配，List采用in匹配 其他类型采用精确匹配
                if (type.isAssignableFrom(String.class)) {
                    queryWrapper.like(name, value);
                } else if (type.isAssignableFrom(Order.class)) {
                    order(queryWrapper, (Order) value);
                } else if (fieldTypeByList) {
                    List<?> list = (List<?>) value;
                    if (!list.isEmpty()) {
                        Class<?> c = list.get(0).getClass();
                        if (c.isAssignableFrom(Order.class)) {
                            order(queryWrapper, (List<Order>) value);
                        } else {
                            queryWrapper.in(name, list);
                        }
                    }
                } else if (f.getName().endsWith("Begin")) {
                    queryWrapper.ge(name.replace("_begin", ""), value);
                } else if (f.getName().endsWith("End")) {
                    queryWrapper.le(name.replace("_end", ""), value);
                } else {
                    eq(queryWrapper, name, value);
                }
            } else {
                String columnName = condition.columnName();
                if (!StringUtils.isEmpty(columnName)) {
                    name = fieldAlias + "`" + SNAKE_CASE_STRATEGY.translate(columnName) + "`";
                }
                ConditionType conditionType = condition.value();
                // 当值为null且有nullHandler时
                if (isNull) {
                    conditionType = condition.nullHandler();
                }
                switch (conditionType) {
                    case EQUAL:
                        eq(queryWrapper, name, value);
                        break;
                    case NULL:
                        queryWrapper.isNull(name);
                        break;
                    case NOT_NULL:
                        queryWrapper.isNotNull(name);
                        break;
                    case LIKE:
                        queryWrapper.like(name, value);
                        break;
                    case GT:
                        queryWrapper.gt(name, value);
                        break;
                    case GT_EQUAL:
                        queryWrapper.ge(name, value);
                        break;
                    case LT_EQUAL:
                        queryWrapper.le(name, value);
                        break;
                    case LT:
                        queryWrapper.lt(name, value);
                        break;
                    case IN:
                    case NOT_IN:
                        if (fieldTypeByList) {
                            List<?> list = (List<?>) value;
                            if (!list.isEmpty()) {
                                if (ConditionType.IN == conditionType) {
                                    queryWrapper.in(name, list);
                                } else {
                                    queryWrapper.notIn(name, list);
                                }
                            }
                        }
                        break;
                    case EXISTS:
                    case NOT_EXISTS:
                        // 默认替换规则
                        String sql = condition.sql();
                        if (value != null) {
                            if (fieldTypeByList) {
                                List<?> list = (List<?>) value;
                                if (!list.isEmpty()) {
                                    sql += " AND " + name + " IN (" + list.stream()
                                            .map(String::valueOf)
                                            .collect(Collectors.joining(",")) + ")";
                                }
                            } else {
                                sql += " AND " + name + "=" + value;
                            }
                        }
                        if (ConditionType.EXISTS == conditionType) {
                            queryWrapper.exists(sql);
                        } else {
                            queryWrapper.notExists(sql);
                        }
                        break;
                    case SQL:
                        queryWrapper.apply("(" + condition.sql().replaceAll("\\$\\{" + f.getName() + "}", "'" + value + "'") + ")");
                        break;
                    case ORDER:
                        if (f.getType().isAssignableFrom(Order.class)) {
                            order(queryWrapper, (Order) value);
                        } else if (fieldTypeByList) {
                            order(queryWrapper, (List<Order>) value);
                        }
                        break;
                    case GROUP:
                        queryWrapper.groupBy(name);
                        break;
                    default:
                        break;
                }
            }
        }
        return queryWrapper;
    }

    private static <T> void eq(QueryWrapper<T> queryWrapper, String name, Object value) {
        queryWrapper.eq(name, value);
    }

    private static <T> void order(QueryWrapper<T> queryWrapper, Order order) {
        if (Boolean.TRUE.equals(order.getAsc())) {
            queryWrapper.orderByAsc(SNAKE_CASE_STRATEGY.translate(order.getName()));
        } else {
            queryWrapper.orderByDesc(SNAKE_CASE_STRATEGY.translate(order.getName()));
        }
    }

    private static <T> void order(QueryWrapper<T> queryWrapper, List<Order> orders) {
        for (Order order : orders) {
            order(queryWrapper, order);
        }
    }

    private static String getAlias(TableAlias tableAlias, String alias) {
        String s = getString(tableAlias);
        if (s != null) {
            return s;
        }
        return alias;
    }

    /**
     * 根据TableAlias注解解析表别名
     *
     * @param tableAlias 表别名注解
     * @return 表别名
     */
    private static String getString(TableAlias tableAlias) {
        if (tableAlias != null) {
            String name = tableAlias.name();
            if (!StringUtils.isEmpty(name)) {
                return name + ".";
            }

            Class<?> entityClass = tableAlias.clazz();
            if (!entityClass.isAssignableFrom(Void.class)) {
                TableName tableName = entityClass.getAnnotation(TableName.class);
                if (tableName != null) {
                    return tableName.value() + ".";
                }
                return SNAKE_CASE_STRATEGY.translate(entityClass.getSimpleName()) + ".";
            }
        }
        return null;
    }
}
